home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Precision Software Appli…tions Silver Collection 1
/
Precision Software Applications Silver Collection Volume One (PSM) (1993).iso
/
windows
/
games
/
xlmath21.arj
/
SOURCE.ZIP
/
XLMATH.C
< prev
next >
Wrap
C/C++ Source or Header
|
1993-04-10
|
21KB
|
851 lines
/*----------------------------------------------------------------------*\
XLMATH
========
Copyright 1992 by Roy Kari
Author: Roy Kari
Dept. of Chemistry
Laurentian University
Sudbury, Ont.
Canada P3E 2C6
(705) 675-1151
Internet: "ROY@NICKEL.LAURENTIAN.CA"
Revision history:
1.0 September, 1992
initial version
2.0 January, 1993
added dialog boxes
2.1 April, 1993
bug fixes
This module contains routines written to interface normal C
routines to an Excel custom function format. All custom
functions are assumed to pass a floating point array to the custom
function, and the custom function returns an XLOPER, usually an
array. If you wish to use a dialog box, then the dialog routine
is written to interface the custom function to a dialog box.
The dialog interface must get the input from the Excel sheet,
change it to a floating point array, and then call the custom function.
On return from the custom function, the dialog interface must
display the return XLOPER in the sheet, and free all allocated
memory.
2nd thoughts;
If I had this to over again, I would make the custom functions
accept their input in the form of XLOPER's rather than a
floating point array. This would eliminate the need to convert.
\*----------------------------------------------------------------------*/
/* --------------------------< Include files >--------------------- */
#include <windows.h>
#include <xlcall.h>
#include <float.h>
#include <math.h>
#include <stdlib.h>
#include <framewrk.h>
#include "xlmath.h"
#include "xlutil.h"
#include "xlmcurve.h"
#include "xlmcfit.h"
REALTYPE predict (int k, PARVEC p, REALTYPE xk);
/********************************************************************
Diagonalize()
=============
Diagonalize a real symmetric matrix. Eigenvalues are returned
*******************************************************************/
LPXLOPER PASCAL FAR __export Diagonalize(LPFP lpHmat)
{
LPMULTI lpMulti = NULL; // return XLOPER's
LPLPREAL lplpHmat; // pointers for EXCEL allocated array
LPREAL lpEivec; // eigenvector array
LPLPREAL lplpEivec; // pointers for above
LPREAL lpEival; // eigenvalue array
WORD i,j,wRowIndex; // index
int nIter; // no iterations in _Diagonalize()
WORD wRows, wCols; // saves typing
BOOL Success = TRUE;
wRows = lpHmat->wRows;
wCols = lpHmat->wCols;
// error if not square array
if (wRows != wCols)
{
ErrorHandler(XLM_NOT_SQUARE);
return (glpxlError);
}
// allocate a Global 2-D array for eigenvectors. This array allocation
// demonstrates the use of 2-D array indexing M[i][j]. It is
// required in this case because the working routine _Diagonalize() was
// written in this manner and it was too much work to convert
// it to anything else.
if ((lpEivec = (LPREAL)GetMem( sizeof(double)*wRows*wCols)) == NULL)
{
return (glpxlError);
}
if ((lplpEivec = InitPointers(lpEivec, wRows, wCols)) == NULL)
{
Success = FALSE;
goto Error3; // free lpEivec
}
// create pointers for Hmat; array allocated in Excel
if ((lplpHmat = InitPointers(&lpHmat->Data[0], wRows, wRows))
== NULL)
{
Success = FALSE;
goto Error2; // free lpEivec & lplpEivec
}
// allocate a Global 1-D array for eigenvalues
if ((lpEival = (LPREAL)GetMem( sizeof(double)*wRows )) == NULL)
{
Success = FALSE;
goto Error1; // free lpEivec, lplpEivec & lplpHmat
}
// diagonalize the matrix
nIter = _Diagonalize(lplpHmat, lplpEivec, lpEival, wRows);
//
// return vales to EXCEL
//
// init MULTI 1 row larger to store eigenvalues in last row
if ((lpMulti =InitMulti(wRows+1, wRows, xltypeNum)) == NULL)
{
Success = FALSE;
}
else
{
// copy the eigenvectors
for (i = 0; i < wRows; i++)
{
wRowIndex = i*wCols;
for (j=0; j < wCols; j++)
{
lpMulti->Data[wRowIndex+j].val.num = lplpEivec[i][j];
}
}
// copy the eigenvalues
wRowIndex = wRows*wCols;
for (j = 0; j < wCols; j++)
{
lpMulti->Data[wRowIndex+j].val.num = lpEival[j];
}
}
// free pointers and arrays
FreeMem((LPVOID)lpEival);
Error1:
MemFreePtr((LPVOID)lplpHmat);
Error2:
MemFreePtr((LPVOID)lplpEivec);
Error3:
FreeMem((LPVOID)lpEivec);
if (!Success)
{
return (glpxlError);
}
// return XLOPER to EXCEL
return ( (LPXLOPER)lpMulti);
}
/********************************************************************
MODensity()
=============
Compute the MO density as C(T)xOxC
*******************************************************************/
LPXLOPER FAR PASCAL __export MODensity(LPFP lpEivec, LPFP lpOcc)
{
LPMULTI lpDen = NULL;
UINT i, j, k;
UINT i_Idx, j_Idx, ij_Idx, NumVec;
BOOL bError = FALSE;
NumVec = lpEivec->wRows;
if (lpOcc->wRows == 1)
{
// Occ is row vector (1xN)
if (lpOcc->wCols != NumVec)
{
bError = TRUE;
}
}
else
{
// Occ is a column vector (Nx1)
if ( (lpOcc->wRows != NumVec) || (lpOcc->wCols != 1) )
{
bError = TRUE;
}
}
if (bError)
{
ErrorHandler(XLM_MODENSITY);
return (glpxlError);
}
if ((lpDen =InitMulti(NumVec, NumVec, xltypeNum)) == NULL)
{
return (glpxlError);
}
i_Idx = 0;
for (i = 0; i < NumVec; i++)
{
j_Idx = 0;
for (j = 0; j < NumVec; j++)
{
ij_Idx = i_Idx+j;
lpDen->Data[ij_Idx].val.num = 0.0;
for (k = 0; k < NumVec; k++)
{
// den[i][j] = Eivec[i][k]*Eivec[j][k]*Occ[k]
lpDen->Data[ij_Idx].val.num += lpEivec->Data[i_Idx+k] *
lpEivec->Data[j_Idx+k] *
lpOcc->Data[k];
}
j_Idx += NumVec;
}
i_Idx += NumVec;
}
return ((LPXLOPER)lpDen);
}
/********************************************************************
PolyCurveFit()
=============
This function is a generalized least squares curve fitting function.
It fits a polynomial with linear coefficients to a dependent -
independent variable set of data
*******************************************************************/
LPXLOPER PASCAL FAR _export PolyCurveFit(LPFP lpIndVar, LPFP lpDepVar,
unsigned int Order)
{
#define nCols 3 // placed in a NumObs x nCols array
LPMULTI lpMulti;
LPREAL lpYest, lpResid, lpPolycoef, lpCoefsig;
static double See, Rsqrval, Rval, dferror;
static WORD wNumObs;
WORD wRows = lpIndVar->wRows;
WORD wCols = lpIndVar->wCols;
WORD wMinRows = 2*(Order+1) + 4; // needed for return values
BOOL Success = TRUE;
BOOL bColumnVector = TRUE;
WORD i, wRow1, wRow2, wRowIndex;
wNumObs = wRows;
if (wCols > wRows)
{
// change to row vectors
bColumnVector = FALSE;
wNumObs = wCols;
}
// check input dimensions so both same
if ( (lpDepVar->wRows != lpIndVar->wRows) ||
(lpDepVar->wCols != lpIndVar->wCols) ||
(Order >= wNumObs - 1)
)
{
ErrorHandler(XLM_POLY);
return(glpxlError);
}
if (wMinRows < wNumObs)
wMinRows = wNumObs;
// memory for estimated Y values
lpYest = (LPREAL)GetMem( wNumObs*sizeof(double));
if (lpYest == NULL)
{
Success = FALSE;
goto Error4;
}
// memory for residuals
if ((lpResid = (LPREAL)GetMem( wNumObs*sizeof(double))) == NULL)
{
Success = FALSE;
goto Error3;
}
//memory for coefficients of fitted polynomial
if ((lpPolycoef = (LPREAL)GetMem( (Order+1)*sizeof(double))) == NULL)
{
Success = FALSE;
goto Error2;
}
// memory for standard errors of coefficient estimates
if ((lpCoefsig = (LPREAL)GetMem( (Order+1)*sizeof(double))) == NULL)
{
Success = FALSE;
goto Error1;
}
Success = _PolyCurveFit((LPREAL)&lpIndVar->Data[0],
(LPREAL)&lpDepVar->Data[0], (int)wNumObs, (int)Order, lpPolycoef,
lpYest, lpResid, (NPREAL)&See, lpCoefsig, &Rsqrval, &Rval, &dferror);
if (!Success)
goto Error0;
// init XLOPER
if (bColumnVector)
lpMulti = InitMulti(wMinRows, nCols, xltypeNum);
else
lpMulti = InitMulti(nCols, wMinRows, xltypeNum);
if (lpMulti==NULL)
{
Success = FALSE;
goto Error0;
}
// stuff return data into Multi
if (bColumnVector)
{
// return as column vectors (NumObs x 3)
wRowIndex = 0;
wRow2 = (Order+1)*nCols+2;
for (i=0; i < wNumObs; i++)
{
lpMulti->Data[wRowIndex].val.num = lpYest[i];
lpMulti->Data[wRowIndex+1].val.num = lpResid[i];
if ( i <= Order )
{
lpMulti->Data[wRowIndex+2].val.num = lpPolycoef[i];
lpMulti->Data[wRowIndex+wRow2].val.num = lpCoefsig[i];
}
wRowIndex += nCols;
}
// remaining variables stuck in array directly below polycoef
wRowIndex = 2*(Order+1);
lpMulti->Data[(wRowIndex )*nCols + 2].val.num = See;
lpMulti->Data[(wRowIndex+1)*nCols + 2].val.num = Rsqrval;
lpMulti->Data[(wRowIndex+2)*nCols + 2].val.num = Rval;
lpMulti->Data[(wRowIndex+3)*nCols + 2].val.num = dferror;
}
else
{
// return as row vectors ( 3 x NumObs)
wRow1 = wNumObs; // index to row 1
wRow2 = wNumObs*2; // index to row 2
for (i=0; i<wNumObs; i++)
{
lpMulti->Data[i].val.num = lpYest[i];
lpMulti->Data[wRow1].val.num = lpResid[i];
++wRow1;
if ( i<= Order)
{
lpMulti->Data[wRow2].val.num = lpPolycoef[i];
lpMulti->Data[wRow2+Order+1].val.num = lpCoefsig[i];
++wRow2;
}
}
// remaining variables
wRow2 = wNumObs*2 + 2*(Order+1);
lpMulti->Data[wRow2].val.num = See;
lpMulti->Data[wRow2+1].val.num = Rsqrval;
lpMulti->Data[wRow2+2].val.num = Rval;
lpMulti->Data[wRow2+3].val.num = dferror;
}
Error0:
FreeMem((LPVOID)lpCoefsig);
Error1:
FreeMem((LPVOID)lpPolycoef);
Error2:
FreeMem((LPVOID)lpResid);
Error3:
FreeMem((LPVOID)lpYest);
Error4:
if (!Success)
{
return (glpxlError);
}
return ((LPXLOPER)lpMulti);
}
#undef nCols
/********************************************************************
CubicSplines()
=============
This function will fit a set of cubic spline polynomial equations
to a discrete set of data.
*******************************************************************/
LPXLOPER PASCAL FAR _export CubicSplines(LPFP lpIndVar, LPFP lpDepVar)
{
#define nCols 4 // placed in a wNumDat-1 x 4 array
LPMULTI lpMulti;
WORD i, j, wIndex;
WORD wNumDat = lpIndVar->wRows; // assume column vector
BOOL Success = TRUE;
LPREAL lpCoef;
// check inputs are row or column vectors
if ( (lpIndVar->wCols > 1 && lpIndVar->wRows > 1) ||
(lpDepVar->wCols > 1 && lpDepVar->wRows > 1) )
{
ErrorHandler(XLM_CUBIC);
return (glpxlError);
}
// check if row or column input
if (lpIndVar->wRows < lpIndVar->wCols)
{
// input is a row vector
wNumDat = lpIndVar->wCols;
}
if ((lpCoef = (LPREAL)GetMem( wNumDat*4*sizeof(realtype))) == NULL)
return (glpxlError);
Success = _CubicSplines((LPREAL)&lpIndVar->Data[0],
(LPREAL)&lpDepVar->Data[0],
(int)(wNumDat-1),
(LPREAL)lpCoef);
if (!Success)
{
FreeMem((LPVOID)lpCoef);
ErrorHandler(XLM_CUBIC);
return (glpxlError);
}
// always return coefs in N-1 x 3 matrix
if ((lpMulti = InitMulti(wNumDat-1, nCols, xltypeNum)) == NULL)
{
FreeMem((LPVOID)lpCoef);
return (glpxlError);
}
for (i=0; i < wNumDat-1; i++)
{
wIndex = i*nCols;
for (j = 0; j < nCols; j++)
{
lpMulti->Data[wIndex + j].val.num = lpCoef[wIndex + j];
}
}
FreeMem((LPVOID)lpCoef);
return ((LPXLOPER)lpMulti);
}
#undef nCols
/********************************************************************
CalcSpline()
=============
This function calculate the cubic spline interpolation of an
y-value given an x-value and the cubic spline coefficients.
*******************************************************************/
LPXLOPER PASCAL FAR __export CalcSpline(LPFP lpXorig, LPFP lpCoef,
LPFP lpXcalc)
{
LPMULTI lpMulti;
WORD wRows;
WORD wCols;
WORD wNumCalcX;
WORD wNumOrigX;
realtype Xmin, Xmax, Xcurrent;
WORD i;
// check orientation of input vectors
if (lpXorig->wRows > lpXorig->wCols)
{
// column vector
wNumOrigX = lpXorig->wRows;
wRows = wNumCalcX = lpXcalc->wRows;
wCols = 1;
}
else
{
// row vector
wNumOrigX = lpXorig->wCols;
wCols = wNumCalcX = lpXcalc->wCols;
wRows = 1;
}
// check limits
if ( (lpCoef->wRows != wNumOrigX - 1) ||
(lpCoef->wCols != 4)
)
{
ErrorHandler(XLM_CALCSPLINE);
return glpxlError;
}
// allocate memory for returned xloper
if ( (lpMulti = InitMulti(wRows, wCols, xltypeNum)) == NULL)
return (glpxlError);
// do the calculations
wNumOrigX = wNumOrigX - 1;;
Xmin = lpXorig->Data[0];
Xmax = lpXorig->Data[wNumOrigX];
for (i=0; i < wNumCalcX; i++)
{
Xcurrent = lpXcalc->Data[i];
if ( (Xcurrent < Xmin) || (Xcurrent > Xmax))
{
lpMulti->Data[i].xltype = xltypeErr;
lpMulti->Data[i].val.err = xlerrValue;
}
else
{
_CalcSpline( (LPREAL)&lpXorig->Data[0],
(LPREAL)&lpCoef->Data[0],
(int)(wNumOrigX),
(LPREAL)&Xcurrent,
(LPREAL) &lpMulti->Data[i].val.num);
}
}
return ((LPXLOPER)lpMulti);
}
/********************************************************************
SmoothSG()
=============
This function uses the Savitsky - Golay algorithm to reduce the
noise in a sampled data set.
*******************************************************************/
LPXLOPER PASCAL FAR _export SmoothSG(LPFP lpData, unsigned int wSmoothNum,
unsigned int wDerivNum)
{
LPMULTI lpMulti;
WORD wRows = lpData->wRows;
WORD wCols = lpData->wCols;
WORD wNumDat;
LPREAL lpCoef;
WORD i;
// check limits
if ( (wSmoothNum < 1) || (wSmoothNum > 5) || (wDerivNum > 2) )
{
ErrorHandler(XLM_SMOOTHSG);
return glpxlError;
}
// set wNumDat & check if row or column vector
wNumDat = wRows > wCols? wRows : wCols;
if (wCols > 1 && wRows > 1)
{
ErrorHandler(XLM_SMOOTHSG);
return (glpxlError);
}
if ((lpCoef = (LPREAL)GetMem(wNumDat*sizeof(double))) == NULL)
return glpxlError;
// call smoothing routine
_DataSmoothSg((LPREAL)&lpData->Data[0],
(int)wNumDat,
(int)wSmoothNum,
(int)wDerivNum,
(LPREAL)lpCoef);
// copy results & return as row or column vector in original
if ((lpMulti = InitMulti(wRows, wCols, xltypeNum)) == NULL)
{
FreeMem((LPVOID)lpCoef);
return glpxlError;
}
else
{
for (i=0; i<wNumDat; i++)
{
lpMulti->Data[i].val.num = lpCoef[i];
}
}
FreeMem((LPVOID)lpCoef);
return ((LPXLOPER)lpMulti);
}
/********************************************************************
SmoothWeights()
=============
This function smooths data via a weighted average
*******************************************************************/
LPXLOPER PASCAL FAR _export SmoothWeights(LPFP lpData,
LPFP lpWeights)
{
LPMULTI lpMulti;
WORD wRows = lpData->wRows;
WORD wCols = lpData->wCols;
WORD wNumDat;
WORD wSmoothNum;
LPREAL lpCoef;
realtype Divisor = 0.0;
WORD i;
// set wNumDat & check if row or column vector
wNumDat = wRows > wCols? wRows : wCols;
if (wCols > 1 && wRows > 1)
{
ErrorHandler(XLM_SMOOTHSG);
return (glpxlError);
}
// take from column or row vector
wSmoothNum = (lpWeights->wRows > lpWeights->wCols) ? lpWeights->wRows :
lpWeights->wCols;
// get normalization factor for weights
for (i=0; i<wSmoothNum; i++)
Divisor += lpWeights->Data[i];
if ((lpCoef = (LPREAL)GetMem(sizeof(double)*wNumDat)) == NULL)
return glpxlError;
_DataSmoothWeights((LPREAL)&lpData->Data[0],
(int)wNumDat,
(int)wSmoothNum,
(LPREAL)&lpWeights->Data[0],
(LPREAL) &Divisor,
(LPREAL)lpCoef);
// return as column or row vector as per original
if ((lpMulti = InitMulti(wRows, wCols, xltypeNum)) == NULL)
{
FreeMem((LPVOID)lpCoef);
return glpxlError;
}
else
{
for (i=0; i<wNumDat; i++)
{
lpMulti->Data[i].val.num = lpCoef[i];
}
}
FreeMem((LPVOID)lpCoef);
return ((LPXLOPER)lpMulti);
}
/********************************************************************
CustomFit()
=============
This function is the interface function to _CustomFit()
*******************************************************************/
LPXLOPER PASCAL FAR __export CustomFit(LPFP lpData, LPFP lpParms)
{
extern LPMULTI lpMultiCfit;
LPMULTI lpMulti;
WORD wRows = lpData->wRows;
WORD wCols = lpData->wCols;
static WORD wNumDat;
static WORD wNumParm;
LPREAL lpY, lpX, lpP, lpUpper, lpLower;
LPREAL lpPopt, lpSdev, lpD, lpEig, lpFit;
WORD i, wRowIndex;
BOOL success = FALSE;
// set wNumDat & check if row or column vector
if (wRows > wCols)
{
// in column vector -- OK
wNumDat = wRows;
if (wCols !=2 || wRows <3)
{
// need 2 cols & more than 2 rows
ErrorHandler(XLM_CF1);
return(glpxlError);
}
}
else
{
// in row vector & NOT allowed
ErrorHandler(XLM_CF2);
return(glpxlError);
}
// Parms must be row vector
wNumParm = lpParms->wCols;
if (lpParms->wRows != 3)
{
// must have 3 rows
ErrorHandler(XLM_CF3);
return(glpxlError);
}
// must have more data points than parms
if (wNumParm > wNumDat)
{
ErrorHandler(XLM_CF8);
return(glpxlError);
}
// allocate memory
if ((lpY = (LPREAL)GetMem(sizeof(double)*wNumDat)) == NULL)
return glpxlError;
if ((lpX = (LPREAL)GetMem(sizeof(double)*wNumDat)) == NULL)
{
FreeMem((LPVOID)lpY);
return glpxlError;
}
if ((lpP = (LPREAL)GetMem(sizeof(double)*wNumParm)) == NULL)
{
FreeMem((LPVOID)lpY);
FreeMem((LPVOID)lpX);
return glpxlError;
}
if ((lpUpper = (LPREAL)GetMem(sizeof(double)*wNumParm)) == NULL)
{
FreeMem((LPVOID)lpY);
FreeMem((LPVOID)lpX);
FreeMem((LPVOID)lpP);
return glpxlError;
}
if ((lpLower = (LPREAL)GetMem(sizeof(double)*wNumParm)) == NULL)
{
FreeMem((LPVOID)lpY);
FreeMem((LPVOID)lpX);
FreeMem((LPVOID)lpP);
FreeMem((LPVOID)lpUpper);
return glpxlError;
}
// extract the data
wRowIndex = 0;
for (i=0; i<2*wNumDat; i+=2)
{
lpX[wRowIndex] = lpData->Data[i];
lpY[wRowIndex] = lpData->Data[i+1];
++wRowIndex;
}
// extract the parms
for (i=0; i<wNumParm; i++)
{
lpP[i] = lpParms->Data[i];
lpUpper[i] = lpParms->Data[i+wNumParm];
lpLower[i] = lpParms->Data[i+2*wNumParm];
}
// allocate fitting variable memory
if ((lpPopt = (LPREAL)GetMem(sizeof(double)*wNumParm)) == NULL)
{
FreeMem((LPVOID)lpPopt);
success = FALSE;
goto abort;
}
if ((lpSdev = (LPREAL)GetMem(sizeof(double)*wNumParm)) == NULL)
{
FreeMem((LPVOID)lpPopt);
success = FALSE;
goto abort;
}
if ((lpD = (LPREAL)GetMem(sizeof(double)*wNumParm)) == NULL)
{
FreeMem((LPVOID)lpPopt);
FreeMem((LPVOID)lpSdev);
success = FALSE;
goto abort;
}
if ((lpEig = (LPREAL)GetMem(sizeof(double)*wNumParm)) == NULL)
{
FreeMem((LPVOID)lpPopt);
FreeMem((LPVOID)lpSdev);
FreeMem((LPVOID)lpD);
success = FALSE;
goto abort;
}
if ((lpFit = (LPREAL)GetMem(sizeof(double)*wNumParm)) == NULL)
{
FreeMem((LPVOID)lpPopt);
FreeMem((LPVOID)lpSdev);
FreeMem((LPVOID)lpD);
FreeMem((LPVOID)lpEig);
success = FALSE;
goto abort;
}
// allocate a multi for calling macro
// strip xlbitDLLFree to prevent Excel from freeing
if ((lpMultiCfit = InitMulti(1, wNumParm, xltypeNum)) == NULL)
goto abort1;
lpMultiCfit = (LPMULTI)StripxlbitDLLFree((LPXLOPER)lpMultiCfit);
success = _CustomFit((int)wNumDat, (int)wNumParm,
lpY, lpX, lpP, lpUpper, lpLower,
lpPopt, lpSdev, lpD, lpEig, lpFit);
if (!success)
{
goto abort2;
}
//
// do output via Multi
//
if ((lpMulti = InitMulti(wNumDat, wNumParm+1, xltypeNum)) == NULL)
{
goto abort2;
}
// calculate fitted values to first column
wRowIndex = 0;
for (i=0; i<wNumDat; i++)
{
lpMulti->Data[wRowIndex].val.num = predict(i, lpPopt, lpX[i]);
wRowIndex +=(wNumParm+1);
}
// copy parameters, std. deviations & eigenvalues
wRowIndex = wNumParm+1;
for (i=0; i<wNumParm; i++)
{
lpMulti->Data[i+1].val.num = lpPopt[i];
lpMulti->Data[wRowIndex+i+1].val.num = lpSdev[i];
lpMulti->Data[2*wRowIndex+i+1].val.num = lpEig[i];
lpMulti->Data[3*wRowIndex+i+1].val.num = lpD[i];
}
abort2:
xlAutoFree((LPXLOPER)lpMultiCfit);
Excel(xlFree, 0, 1, (LPXLOPER)&xMacroRef);
abort1:
FreeMem(lpFit);
FreeMem(lpEig);
FreeMem(lpD);
FreeMem(lpSdev);
FreeMem(lpPopt);
abort:
FreeMem((LPVOID)lpY);
FreeMem((LPVOID)lpX);
FreeMem((LPVOID)lpP);
FreeMem((LPVOID)lpUpper);
FreeMem((LPVOID)lpLower);
if (success)
return ((LPXLOPER)lpMulti);
else
{
return(glpxlError);
}
}